package com.violet.uaa.server.login.service.impl;

import com.violet.auth.client.util.JwtTokenUtil;
import com.violet.common.entity.LoginUser;
import com.violet.common.entity.Role;
import com.violet.common.exception.BusinessException;
import com.violet.common.util.ObjectCopyUtil;
import com.violet.redis.constant.RedisKeyPrefixConstant;
import com.violet.redis.constant.RedisKeyUtil;
import com.violet.redis.template.RedisRepository;
import com.violet.uaa.server.auth.mapper.RoleMapper;
import com.violet.uaa.server.login.service.VioletUserDetailsService;
import com.violet.user.api.user.dto.UserDTO;
import com.violet.user.api.user.service.UserApiService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @Author: wqf
 * @Date: 2023/06/13
 * @Description:
 */
@Slf4j
@Service
public class UserDetailServiceImpl implements VioletUserDetailsService {

    @DubboReference
    private UserApiService userApiService;
    @Resource
    private RedisRepository redisRepository;
    @Resource
    private JwtTokenUtil jwtTokenUtil;
    @Resource
    private RoleMapper roleMapper;

    /**
     * 方法描述: 加载当前登陆用户信息级权限信息
     * 默认情况下，当 Spring Security 对用户进行认证时，首先会将用户的信息包装成一个 Authentication 对象，该对象仅包含一些基本信息（如用户名、密码等），
     * 之后 Spring Security 将交由 AuthenticationManager 中的 AuthenticationProvider 来对该 Authentication 进行认证处理，
     * 认证成功后将重置该对象的认证状态并为该对象赋值一些其他信息（如权限等信息），之后将该对象存入 SecurityContextHolder 中。
     * Authentication 接口在 Spring Security 中主要有两个作用。
     * （1）封装用户的信息：用于传递给 AuthenticationManager 进行认证处理。
     * （2）代表当前认证的用户：当用户认证成功后，Authentication 将存储我们认证的所有信息
     * <p>
     * 该实现作用
     * 1.查找用户：根据提供的用户名从数据库或其他存储中查找用户信息。
     * 2.返回用户详情：将查找到的用户信息封装成 UserDetails对象，供 Spring Security 在认证和授权过程中使用。
     * 3.处理异常：如果找不到对应的用户，通常会抛出  UsernameNotFoundException  异常，以通知 Spring Security 认证失败。
     *
     * @param username 用户名
     * @return org.springframework.security.core.userdetails.UserDetails
     * @author wqf
     * @date 2023/6/13 16:59
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //查询用户信息
        UserDTO userDTO = userApiService.getUserByUserName(username);
        if (userDTO == null) {
            log.info("=======用户名或密码错误======={}", username);
            throw new UsernameNotFoundException(username);
        }
        if (!userDTO.isEnabled()) {
            throw new DisabledException("用户已作废");
        }
        LoginUser loginUser = ObjectCopyUtil.copyProperties(userDTO, LoginUser::new);
        Long userId = loginUser.getId();
        //返回用户信息 给spring security做权限校验
        List<Role> roles = roleMapper.selectByUserId(userId);
        loginUser.setRoles(roles);
        return loginUser;
    }

    /**
     * 方法描述: 保存验证码
     *
     * @param codeKey 机器码 前端给验证码的唯一标识
     * @param code    验证码文本
     * @author wqf
     * @date 2023/6/14 14:41
     */
    @Override
    public void saveImageCode(String codeKey, String code) {
        if (codeKey == null || codeKey.isEmpty()) {
            log.error("========================缺少机器码========================");
            throw new BusinessException("非法请求");
        }
        String cacheKey = RedisKeyUtil.getCacheKey(RedisKeyPrefixConstant.Validate.VALIDATE_IMG_KEY, codeKey);
        //将验证码缓存到redis，并设置过期时间为2分钟
        redisRepository.setExpire(cacheKey, code, 60 * 2L, TimeUnit.SECONDS);
    }

    @Override
    public boolean supports(String accountType) {
        return "admin".equals(accountType);
    }
}
